﻿using Noria.Net.Chaos.Properties;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Noria.Net.Chaos.Net
{
    class PortMapper : IDisposable
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private TcpListener listener;
        private IList<ForwardWorker> workers = new List<ForwardWorker>();
        private SemaphoreSlim semaphore;
        private bool stopped = false;
        private ushort from;
        private ushort to;

        public PortMapper(ushort from, ushort to, int maxConcurrency)
        {
            this.from = from;
            this.to = to;
            this.listener = TcpListener.Create(from);
            this.semaphore = new SemaphoreSlim(0, maxConcurrency);
        }

        public void Dispose()
        {
            semaphore.Dispose();
        }

        internal void StartListen()
        {
            log.InfoFormat("Forward {0} -> {1}", from, to);

            listener.Start();
            AsyncAccept();
        }

        private async void AsyncAccept()
        {
            // check the INT signal
            if (stopped)
                return;

            // wait for accept by client
            TcpClient client = null;
            try
            {
                client = await listener.AcceptTcpClientAsync();
                log.DebugFormat("Accept client -> {0}", client.Client.RemoteEndPoint);
            }
            catch (ObjectDisposedException)
            {
                log.Debug("Listener has been stopped, accept operation cancelled");
                return;
            }

            // wait for connect to server
            var server = new TcpClient();
            await server.ConnectAsync("localhost", to);
            log.DebugFormat("Connect to server -> {0}", server.Client.RemoteEndPoint);

            // create a worker (start work immediately) and put it into the queue
            workers.Add(new ForwardWorker(semaphore, client, server));

            // call the Accept once again
            AsyncAccept();
        }

        internal void StopListen()
        {
            stopped = true;
            listener.Stop();

            foreach (var worker in workers)
            {
                worker.Cancel();
            }
        }

        internal void WaitAll()
        {
            // TODO:
            Thread.Sleep(2000);

            //if (!semaphore.Wait(5000))
            //{
            //    throw new TimeoutException("Some of the forward workers haven't stopped within the specified time interval");
            //}
        }
    }
}
